home *** CD-ROM | disk | FTP | other *** search
Text File | 1991-07-16 | 50.9 KB | 1,346 lines |
- ;; -*- Mode:Lisp; Package:CLIO-OPEN; Base:10; Lowercase:T; Fonts:(CPTFONT); Syntax:Common-Lisp -*-
-
-
- ;;;----------------------------------------------------------------------------------+
- ;;; |
- ;;; TEXAS INSTRUMENTS INCORPORATED |
- ;;; P.O. BOX 149149 |
- ;;; AUSTIN, TEXAS 78714 |
- ;;; |
- ;;; Copyright (C) 1989, 1990 Texas Instruments Incorporated. |
- ;;; |
- ;;; Permission is granted to any individual or institution to use, copy, modify, and |
- ;;; distribute this software, provided that this complete copyright and permission |
- ;;; notice is maintained, intact, in all copies and supporting documentation. |
- ;;; |
- ;;; Texas Instruments Incorporated provides this software "as is" without express or |
- ;;; implied warranty. |
- ;;; |
- ;;;----------------------------------------------------------------------------------+
-
-
-
- (in-package "CLIO-OPEN")
-
- (export '(
- make-scroller
- scale-increment
- scale-indicator-size
- scale-maximum
- scale-minimum
- scale-orientation
- scale-update
- scale-update-delay
- scale-value
- scroller
- )
- 'clio-open)
-
-
- ;;;----------------------------------------------------------------------------+
- ;;; |
- ;;; Scroller |
- ;;; |
- ;;;----------------------------------------------------------------------------+
-
-
- ;; Implementation Strategy:
- ;;
- ;; Elevator and anchor controls are implemented as non-contact subwindows (i.e.
- ;; scroller is NOT a composite). Overall, this strategy simplifies the tasks
- ;; of determining which control is receiving input and of confining the pointer
- ;; cursor to controls during continuous scrolling, without unnecessarily
- ;; incurring the full cost of a sub-contact.
-
- ;; The elevator is represented as an :input-output subwindow; the imagery of all
- ;; elevator controls is drawn to this subwindow. The less/more arrow and drag
- ;; area controls are represented as :input-only subwindows of the elevator
- ;; subwindow. Subwindows are used for these controls only for use as :confine-to
- ;; windows while the pointer is grabbed.
-
- ;; All control subwindows are recorded in the regions vector of the scroller.
- ;; The scroller subwindow receiving a :button-press is determined from the child
- ;; slot of the button event. During event handling, the regions vector is
- ;; searched to look up the vector index of the event window (see FIND-REGION).
- ;; If the event child is the elevator, then a further search based on elevator
- ;; geometry is necessary to determine which elevator subwindow is the event
- ;; window. The resulting vector index is used to select an element from a
- ;; vector of functions to handle the :button-press (see PRESS-HANDLERS).
-
-
- (defcontact scroller (core contact)
- ((increment :type number
- :reader scale-increment ; setf defined below
- :initarg :increment
- :initform 1)
-
- (indicator-size
- :type (or number (member :off))
- :reader scale-indicator-size ; setf defined below
- :initarg :indicator-size
- :initform 0)
-
- (maximum :type number
- :reader scale-maximum ; setf defined below
- :initarg :maximum
- :initform 1)
-
- (minimum :type number
- :reader scale-minimum ; setf defined below
- :initarg :minimum
- :initform 0)
-
- (orientation :type (member :horizontal :vertical)
- :reader scale-orientation ; setf defined below
- :initarg :orientation
- :initform :vertical)
-
- (update-delay :type (or number (member :until-done))
- :reader scale-update-delay ; setf defined below
- :initarg :update-delay
- :initform 0)
-
- (value :type number
- :reader scale-value ; setf defined below
- :initarg :value
- :initform 0)
-
- (compress-exposures
- :initform :on
- :type (member :off :on)
- :reader contact-compress-exposures
- :allocation :class)
-
- (regions :type (vector window)
- :initform (make-array 6)))
-
- (:resources
- increment indicator-size maximum minimum orientation update-delay value
- (border-width :initform 0)
- (event-mask :initform #.(make-event-mask :pointer-motion-hint :exposure))))
-
-
- ;; Index values for accessing region vector and press handler vector
- (defconstant *elevator-region* 0)
- (defconstant *min-anchor-region* 1)
- (defconstant *max-anchor-region* 2)
- (defconstant *less-arrow-region* 3)
- (defconstant *drag-area-region* 4)
- (defconstant *more-arrow-region* 5)
- (defconstant *cable-region* 6)
-
-
- ;;;----------------------------------------------------------------------------+
- ;;; |
- ;;; Initialization |
- ;;; |
- ;;;----------------------------------------------------------------------------+
-
- (defun make-scroller (&rest initargs &key &allow-other-keys)
- (apply #'make-contact 'scroller initargs))
-
-
- (defmethod initialize-instance :after ((self scroller) &key &allow-other-keys)
- (with-slots (width height) self
- ;; Initialize required geometry
- (multiple-value-setq (width height) (preferred-size self))))
-
- (defun min-anchor-geometry (dimensions scroller orientation width height)
- (declare (ignore scroller))
- (let ((anchor-width (scrollbar-anchor-width dimensions))
- (anchor-height (scrollbar-anchor-height dimensions)))
- (if (eq orientation :vertical)
- (values (pixel-round (- width anchor-width) 2) 0 (- anchor-width 2) (- anchor-height 2))
- (values 0 (pixel-round (- height anchor-width) 2) (- anchor-height 2) (- anchor-width 2)))))
-
- (defun max-anchor-geometry (dimensions scroller orientation width height)
- (declare (ignore scroller))
- (let ((anchor-width (scrollbar-anchor-width dimensions))
- (anchor-height (scrollbar-anchor-height dimensions)))
- (if (eq orientation :vertical)
- (values (pixel-round (- width anchor-width) 2) (- height anchor-height) (- anchor-width 2) (- anchor-height 2))
- (values (- width anchor-height) (pixel-round (- height anchor-width) 2) (- anchor-height 2) (- anchor-width 2)))))
-
- (defun elevator-geometry (dimensions scroller orientation width height)
- (let ((anchor-width (scrollbar-anchor-width dimensions)))
- (if (eq orientation :vertical)
- (values (pixel-round (- width anchor-width) 2) (scroller-value-position scroller) anchor-width (scrollbar-elevator-size scroller))
- (values (scroller-value-position scroller) (pixel-round (- height anchor-width) 2) (scrollbar-elevator-size scroller) anchor-width))))
-
- (defun less-arrow-geometry (dimensions scroller orientation width height)
- (declare (ignore scroller orientation width height))
- (let ((anchor-width (scrollbar-anchor-width dimensions)))
- (values 0 0 anchor-width anchor-width)))
-
- (defun more-arrow-geometry (dimensions scroller orientation width height)
- (declare (ignore width height))
- (let ((anchor-width (scrollbar-anchor-width dimensions))
- (abbreviated-p (scrollbar-abbreviated-p scroller)))
- (if (eq orientation :vertical)
- (values 0 (+ anchor-width (if abbreviated-p 0 anchor-width)) anchor-width anchor-width)
- (values (+ anchor-width (if abbreviated-p 0 anchor-width)) 0 anchor-width anchor-width))))
-
- (defun drag-area-geometry (dimensions scroller orientation width height)
- (declare (ignore scroller width height))
- (let ((anchor-width (scrollbar-anchor-width dimensions)))
- (if (eq orientation :vertical)
- (values 0 anchor-width anchor-width anchor-width)
- (values anchor-width 0 anchor-width anchor-width))))
-
- (defmethod reconfigure-controls ((self scroller))
- (declare (type scroller self))
- (with-slots (regions width height orientation)
- self ;(the scroller self)
- (let ((dimensions (getf *scrollbar-dimensions* (contact-scale self))))
- (let ((window (svref regions *min-anchor-region*)))
- (with-state (window)
- (multiple-value-bind (window-x window-y window-width window-height)
- (min-anchor-geometry dimensions self orientation width height)
- (setf (drawable-x window) window-x
- (drawable-y window) window-y
- (drawable-width window) window-width
- (drawable-height window) window-height))))
- (let ((window (svref regions *max-anchor-region*)))
- (with-state (window)
- (multiple-value-bind (window-x window-y window-width window-height)
- (max-anchor-geometry dimensions self orientation width height)
- (setf (drawable-x window) window-x
- (drawable-y window) window-y
- (drawable-width window) window-width
- (drawable-height window) window-height))))
- (let ((window (svref regions *elevator-region*)))
- (with-state (window)
- (multiple-value-bind (window-x window-y window-width window-height)
- (elevator-geometry dimensions self orientation width height)
- (setf (drawable-x window) window-x
- (drawable-y window) window-y
- (drawable-width window) window-width
- (drawable-height window) window-height))))
- (let ((window (svref regions *drag-area-region*)))
- (with-state (window)
- (multiple-value-bind (window-x window-y window-width window-height)
- (drag-area-geometry dimensions self orientation width height)
- (setf (drawable-x window) window-x
- (drawable-y window) window-y
- (drawable-width window) window-width
- (drawable-height window) window-height))))
- (let ((window (svref regions *less-arrow-region*)))
- (with-state (window)
- (multiple-value-bind (window-x window-y window-width window-height)
- (less-arrow-geometry dimensions self orientation width height)
- (setf (drawable-x window) window-x
- (drawable-y window) window-y
- (drawable-width window) window-width
- (drawable-height window) window-height))))
- (let ((window (svref regions *more-arrow-region*)))
- (with-state (window)
- (multiple-value-bind (window-x window-y window-width window-height)
- (more-arrow-geometry dimensions self orientation width height)
- (setf (drawable-x window) window-x
- (drawable-y window) window-y
- (drawable-width window) window-width
- (drawable-height window) window-height)))))))
-
- (defmethod realize :after ((self scroller))
- ;; Create control region windows
- (with-slots (regions width height orientation foreground)
- self
- (let* ((dimensions (getf *scrollbar-dimensions* (contact-scale self)))
-
- (min-anchor (multiple-value-bind (region-x region-y region-width region-height)
- (min-anchor-geometry dimensions self orientation width height)
- (create-window
- :parent self
- :x region-x
- :y region-y
- :width region-width
- :height region-height
- :background :parent-relative
- :border-width 1
- :border foreground
- :gravity (if (eq orientation :vertical) :north :west))))
-
- (max-anchor (multiple-value-bind (region-x region-y region-width region-height)
- (max-anchor-geometry dimensions self orientation width height)
- (create-window
- :parent self
- :x region-x
- :y region-y
- :width region-width
- :height region-height
- :background :parent-relative
- :border-width 1
- :border foreground
- :gravity (if (eq orientation :vertical) :north :west))))
-
- (elevator (multiple-value-bind (region-x region-y region-width region-height)
- (elevator-geometry dimensions self orientation width height)
- (create-window
- :parent self
- :x region-x
- :y region-y
- :width region-width
- :height region-height
- :border-width 0
- :gravity (if (eq orientation :vertical) :north :west))))
-
- (drag-area (multiple-value-bind (region-x region-y region-width region-height)
- (drag-area-geometry dimensions self orientation width height)
- (create-window
- :parent elevator
- :class :input-only
- :x region-x
- :y region-y
- :width region-width
- :height region-height
- :border-width 0)))
-
- (less-arrow (multiple-value-bind (region-x region-y region-width region-height)
- (less-arrow-geometry dimensions self orientation width height)
- (create-window
- :parent elevator
- :class :input-only
- :x region-x
- :y region-y
- :width region-width
- :height region-height
- :border-width 0)))
-
- (more-arrow (multiple-value-bind (region-x region-y region-width region-height)
- (more-arrow-geometry dimensions self orientation width height)
- (create-window
- :parent elevator
- :class :input-only
- :x region-x
- :y region-y
- :width region-width
- :height region-height
- :border-width 0))))
-
- (setf (svref regions *min-anchor-region*) min-anchor)
- (setf (svref regions *max-anchor-region*) max-anchor)
- (setf (svref regions *elevator-region*) elevator)
- (setf (svref regions *drag-area-region*) drag-area)
- (setf (svref regions *less-arrow-region*) less-arrow)
- (setf (svref regions *more-arrow-region*) more-arrow)
-
- (map-subwindows self)
- (map-subwindows elevator))))
-
- (defmethod rescale ((self scroller))
- (with-slots (orientation)
- self
- ;; Request change to preferred width/height, depending on orientation.
- (multiple-value-bind (rw rh) (if (eq :vertical orientation) (values 0 nil) (values nil 0))
- (multiple-value-bind (pw ph) (preferred-size self :width rw :height rh)
- (change-geometry self :width pw :height ph :accept-p t))))
-
- (when (realized-p self)
- (reconfigure-controls self)))
-
- (defmethod (setf scale-orientation) (new-orientation (scroller scroller))
- (with-slots (orientation width height)
- scroller
- (unless (eq orientation new-orientation)
- (check-type new-orientation (member :horizontal :vertical))
-
- (setf orientation new-orientation)
-
- (multiple-value-bind (new-width new-height)
- (preferred-size scroller :width height :height width)
- (change-geometry scroller :width new-width :height new-height))
- (reconfigure-controls scroller)))
-
- new-orientation)
-
-
- ;;;----------------------------------------------------------------------------+
- ;;; |
- ;;; Accessors |
- ;;; |
- ;;;----------------------------------------------------------------------------+
-
-
- (defmethod (setf scale-update-delay) (new-update-delay (scroller scroller))
- (with-slots (update-delay) scroller
- (assert (or (eq new-update-delay :until-done)
- (and (numberp new-update-delay) (not (minusp new-update-delay)))) ()
- "~a is neither :UNTIL-DONE or a non-negative number." new-update-delay)
- (setf update-delay new-update-delay)))
-
- (defmethod (setf scale-value) (new-value (scroller scroller))
- (scale-update scroller :value new-value)
- new-value)
-
- (defmethod (setf scale-minimum) (new-minimum (scroller scroller))
- (scale-update scroller :minimum new-minimum)
- new-minimum)
-
- (defmethod (setf scale-maximum) (new-maximum (scroller scroller))
- (scale-update scroller :maximum new-maximum)
- new-maximum)
-
- (defmethod (setf scale-increment) (new-increment (scroller scroller))
- (scale-update scroller :increment new-increment)
- new-increment)
-
- (defmethod (setf scale-indicator-size) (new-indicator-size (scroller scroller))
- (scale-update scroller :indicator-size new-indicator-size)
- new-indicator-size)
-
- (defmacro true-indicator-size (size)
- `(if (eq ,size :off) 0 ,size))
-
- (defmethod scale-update ((scroller scroller) &key value minimum maximum indicator-size increment)
- (with-slots
- ((current-val value)
- (current-min minimum)
- (current-max maximum)
- (current-ind indicator-size)
- (current-inc increment)
- regions
- orientation)
- scroller
-
-
- (setf minimum (or minimum current-min)
- maximum (or maximum current-max)
- value (or value current-val)
- indicator-size (or indicator-size current-ind)
- increment (or increment current-inc))
-
- (assert (numberp value) () "~s for :value is not a number" value)
- (assert (numberp minimum) () "~s for :minimum is not a number" minimum)
- (assert (numberp maximum) () "~s for :maximum is not a number" maximum)
- (assert (and (numberp increment) (not (minusp increment))) ()
- "~s for :increment is not a number" increment)
- (assert (or (and (numberp indicator-size) (not (minusp indicator-size)))
- (eq indicator-size :off))
- () "~s for :indicator-size is not :off or a non-negative-number)" indicator-size)
-
-
- (assert (<= minimum maximum) ()
- "Minimum (~a) is greater than maximum (~a)."
- minimum maximum)
- (assert (<= minimum value maximum) ()
- "Value (~a) must be in the range [~a, ~a]."
- value minimum maximum)
-
- (let*
- ((insensitive-p (not (sensitive-p scroller)))
- (less-arrow-dim-p (or insensitive-p (= current-val current-min)))
- (more-arrow-dim-p (or insensitive-p (= current-val current-max)))
- (prev-min current-min)
- (prev-max current-max)
- (prev-ind current-ind)
- (prev-val current-val))
-
- (setf current-min minimum
- current-max maximum
- current-val value
- current-ind indicator-size
- current-inc increment)
-
- ;; Update display
- (when (realized-p scroller)
- (cond
- ((not (and (eql current-min prev-min) (eql current-max prev-max) (eql current-ind prev-ind)))
- (display scroller))
-
- ((not (eql current-val prev-val))
-
- ;; Position elevator
- (let ((position (scroller-value-position scroller))
- (elevator (svref regions *elevator-region*)))
- (if (eq :vertical orientation)
- (setf (drawable-y elevator) position)
- (setf (drawable-x elevator) position)))
-
- ;; Dim arrows, if necessary
- (scrollbar-update-less-arrow scroller less-arrow-dim-p insensitive-p)
- (scrollbar-update-more-arrow scroller more-arrow-dim-p insensitive-p)))))))
-
- (defmethod (setf contact-foreground) :after (new-fg (self scroller))
- (declare (ignore new-fg))
- (with-slots (foreground regions) self
- (setf (window-border (svref regions *min-anchor-region*)) foreground)
- (setf (window-border (svref regions *max-anchor-region*)) foreground)))
-
-
-
- ;;;----------------------------------------------------------------------------+
- ;;; |
- ;;; Geometry Management |
- ;;; |
- ;;;----------------------------------------------------------------------------+
-
-
- (defmethod preferred-size ((self scroller) &key width height border-width)
- (declare (ignore border-width))
- (with-slots (orientation (current-height height) (current-width width)) self
- (let*
- ((dimensions (getf *scrollbar-dimensions* (contact-scale self)))
- (margin (scrollbar-margin dimensions))
- (anchor-width (scrollbar-anchor-width dimensions))
- (anchor-height (scrollbar-anchor-height dimensions))
-
- ;; Calculate geometry assuming :vertical orientation
- (preferred-width (+ margin anchor-width margin))
- (preferred-height (max
- ;; Suggested or current height
- (or (if (eq orientation :vertical) height width)
- (if (eq orientation :vertical) current-height current-width))
-
- ;; Size of abbreviated scrollbar (no drag area)
- (+ anchor-height margin
- anchor-width anchor-width
- margin anchor-height))))
-
- ;; Return preferred geometry according to actual orientation
- (values
- (if (eq orientation :vertical) preferred-width preferred-height)
- (if (eq orientation :vertical) preferred-height preferred-width)
- 0))))
-
- (defmethod resize :around ((self scroller) new-width new-height new-border-width)
- (if (realized-p self)
- ;; Reconfigure subwindows
- (let* ((abbreviated-before-p (scrollbar-abbreviated-p self))
- (resized-p (call-next-method)))
- (with-slots (width height orientation regions) self
- (let*
- ((scale (contact-scale self))
- (max-anchor (svref regions *max-anchor-region*))
- (dimensions (getf *scrollbar-dimensions* scale))
- (anchor-position (- (if (eq orientation :vertical) height width)
- (scrollbar-anchor-height dimensions)
- 1))
- (elevator (svref regions *elevator-region*))
- (elevator-position (scroller-value-position self)))
-
- ;; Reposition max anchor
- (if (eq orientation :vertical)
- (setf (drawable-y max-anchor) anchor-position)
- (setf (drawable-x max-anchor) anchor-position))
-
- ;; Reconfigure elevator
- (multiple-value-bind (elevator-size abbreviated-after-p)
- (scrollbar-elevator-size self)
- (with-state (elevator)
- (case orientation
- (:vertical
- (setf (drawable-y elevator) elevator-position)
- (setf (drawable-height elevator) elevator-size))
-
- (:horizontal
- (setf (drawable-x elevator) elevator-position)
- (setf (drawable-width elevator) elevator-size))))
-
- ;; Changing abbreviation?
- (unless (eq abbreviated-before-p abbreviated-after-p)
- ;; Reposition more-arrow region
- (let* ((anchor-width (scrollbar-anchor-width dimensions))
- (more-arrow-pos (+ anchor-width (if abbreviated-after-p 0 anchor-width))))
- (if (eq orientation :vertical)
- (setf (drawable-y (svref regions *more-arrow-region*)) more-arrow-pos)
- (setf (drawable-x (svref regions *more-arrow-region*)) more-arrow-pos)))
-
- ;; Redisplay elevator image
- (scrollbar-display-elevator self scale)))))
- resized-p)
-
- ;; If not yet realized, just do it
- (call-next-method)))
-
-
-
- (defun scrollbar-abbreviated-p (scroller)
- (with-slots (width height orientation) (the scroller scroller)
- (let*
- ((dimensions (getf *scrollbar-dimensions* (contact-scale scroller)))
- (margin (scrollbar-margin dimensions))
- (anchor-width (scrollbar-anchor-width dimensions))
- (anchor-height (scrollbar-anchor-height dimensions)))
-
- (<= (if (eq orientation :vertical) height width)
-
- (+ anchor-height margin
- anchor-width anchor-width anchor-width
- margin anchor-height)))))
-
-
- (defun scrollbar-elevator-size (scroller)
- (let ((abbreviated-p (scrollbar-abbreviated-p scroller)))
- (values
- (+ (* (scrollbar-anchor-width
- (getf *scrollbar-dimensions* (contact-scale scroller)))
- (if abbreviated-p 2 3))
- 2)
- abbreviated-p)))
-
- (defun scrollbar-less-arrow-geometry (scroller)
- (let ((arrow-size (1- (scrollbar-anchor-width (getf *scrollbar-dimensions*
- (contact-scale scroller))))))
- (if (eq :vertical (scale-orientation scroller))
- (values 1 1 (- arrow-size 2) arrow-size)
- (values 1 1 arrow-size (- arrow-size 2)))))
-
- (defun scrollbar-drag-area-geometry (scroller)
- (let*
- ((area-size (scrollbar-anchor-width
- (getf *scrollbar-dimensions* (contact-scale scroller))))
- (area-position (1+ area-size)))
-
- (if (eq :vertical (scale-orientation scroller))
- (values 1 area-position (- area-size 3) (1- area-size))
- (values area-position 1 (1- area-size) (- area-size 3)))))
-
- (defun scrollbar-more-arrow-geometry (scroller)
- (let*
- ((arrow-size (scrollbar-anchor-width
- (getf *scrollbar-dimensions* (contact-scale scroller))))
- (arrow-position (1+ (+ arrow-size (if (scrollbar-abbreviated-p scroller) 0 arrow-size)))))
-
- (if (eq :vertical (scale-orientation scroller))
- (values 1 arrow-position (- arrow-size 3) (1- arrow-size))
- (values arrow-position 1 (1- arrow-size) (- arrow-size 3)))))
-
-
-
- ;;;----------------------------------------------------------------------------+
- ;;; |
- ;;; Display |
- ;;; |
- ;;;----------------------------------------------------------------------------+
-
- (defmethod display ((self scroller) &optional at-x at-y at-width at-height &key)
- (with-slots (width height foreground regions orientation) self
- ;; Default exposed rectangle, if necessary
- (setf at-x (or at-x 0)
- at-y (or at-y 0)
- at-width (or at-width (- width at-x))
- at-height (or at-height (- height at-y)))
- (let*
- ((scale (contact-scale self))
- (dimensions (getf *scrollbar-dimensions* scale))
- (anchor-width (scrollbar-anchor-width dimensions))
- (anchor-height (scrollbar-anchor-height dimensions))
- (margin (scrollbar-margin dimensions))
- (cable-margin (scrollbar-cable-margin dimensions))
- (cable-width (scrollbar-cable-width dimensions))
- (elevator-position (scroller-value-position self))
- (elevator-size (scrollbar-elevator-size self))
- (elevator-end (+ elevator-position elevator-size)))
-
- ;;-----------------------------------------------------------------------------+
- ;; |
- ;; Draw cable. |
- ;; |
- ;; Stipple fill is relatively slow, so redrawing entire cable can cause |
- ;; annoying flicker. But computing the minimal cable area to redraw is |
- ;; complicated, because the display method is expected to update the image |
- ;; when the elevator moves (thus exposing a small region of the scroller |
- ;; previously obscured by the elevator). In this case, we must redraw the |
- ;; cable not only in the area exposed, but also elsewhere to cover up the |
- ;; previous gaps between elevator/proportion indicator/cable. |
- ;; |
- ;; The following algorithm is a compromise. If the exposed area is entirely |
- ;; on one side of the elevator (as it is in the case of an elevator move), |
- ;; then we redraw the cable only on that side. |
- ;; |
- ;;-----------------------------------------------------------------------------+
-
- (flet
- ((exposed-cable-segment
- (exposed-position exposed-size scroller-size)
- (let ((min (+ anchor-height margin))
- (max (- scroller-size margin anchor-height)))
- (cond
- ;; Exposed area before elevator?
- ((>= elevator-position (+ exposed-position exposed-size))
- ;; Redraw only first part of cable.
- (values min (- elevator-position min)))
-
- ;; Exposed area behind elevator?
- ((>= exposed-position elevator-end)
- ;; Redraw only last part of cable.
- (values elevator-end (- max elevator-end)))
-
- (t
- ;; Redraw all of cable.
- (values min (- max min)))))))
-
- (multiple-value-bind (cable-x cable-y cable-width cable-height)
- (if (eq orientation :vertical)
- (multiple-value-bind (cy ch) (exposed-cable-segment at-y at-height height)
- (values (pixel-round (- width cable-width) 2) cy cable-width ch))
-
- (multiple-value-bind (cx cw) (exposed-cable-segment at-x at-width width)
- (values cx (pixel-round (- height cable-width) 2) cw cable-width)))
-
- ;; Draw exposed cable area
- (using-gcontext (gc :drawable self
- :fill-style :stippled
- :foreground foreground
- :stipple (contact-image-mask self 50%gray :depth 1))
- (clear-area self :x cable-x :y cable-y :width cable-width :height cable-height)
- (draw-rectangle self gc cable-x cable-y cable-width cable-height :fill-p))
-
- ;; Draw proportion indicator
- (let* ((pi-size (scroller-indicator-size self))
- (pi-pos (scroller-indicator-position self pi-size)))
- (multiple-value-bind (pi-x pi-y pi-width pi-height margin-x margin-y margin-width margin-height)
- (if (eq orientation :vertical)
- (values
- cable-x
- pi-pos
- cable-width
- pi-size
-
- cable-x
- (- pi-pos margin)
- cable-width
- (+ pi-size margin margin))
- (values
- pi-pos
- cable-y
- pi-size
- cable-height
-
- (- pi-pos margin)
- cable-y
- (+ pi-size margin margin)
- cable-height))
- (clear-area self :x margin-x :y margin-y :width margin-width :height margin-height)
- (using-gcontext (gc :drawable self
- :fill-style :solid
- :foreground foreground)
- (draw-rectangle self gc pi-x pi-y pi-width pi-height :fill-p))))))
-
- ;; Clear cable margin around elevator
- (multiple-value-bind (gap-x gap-y gap-width gap-height)
- (if (eq orientation :vertical)
- (values
- 0 (- elevator-position cable-margin)
- nil (+ cable-margin elevator-size cable-margin))
- (values
- (- elevator-position cable-margin) 0
- (+ cable-margin elevator-size cable-margin) nil))
- (clear-area self :x gap-x :y gap-y :width gap-width :height gap-height))
-
- ;; Compute elevator geometry
- (multiple-value-bind (elevator-x elevator-y elevator-width elevator-height)
- (if (eq orientation :vertical)
- (values
- (scrollbar-margin dimensions)
- elevator-position
- anchor-width
- elevator-size)
- (values
- elevator-position
- (scrollbar-margin dimensions)
- elevator-size
- anchor-width))
- (when
- ;; Exposed area intersects elevator?
- (and (< elevator-x (+ at-x at-width))
- (< elevator-y (+ at-y at-height))
- (> (+ elevator-x elevator-width) at-x)
- (> (+ elevator-y elevator-height) at-y))
-
- (scrollbar-display-elevator self scale))))))
-
-
-
- (defun scrollbar-display-elevator (scroller &optional scale)
- (setf scale (or scale (contact-scale scroller)))
-
- (with-slots (orientation regions foreground) (the scroller scroller)
- ;; Draw elevator image
- (let*
- ((image (getf (getf *scrollbar-images* orientation) scale))
- (mask (contact-image-mask
- scroller image
- :foreground foreground
- :background (contact-current-background-pixel scroller)))
- (elevator (svref regions *elevator-region*)))
-
- (using-gcontext (gc :drawable scroller :exposures :off)
- (copy-area
- mask gc
- 0 0
- (image-width image) (image-height image)
- elevator
- 0 0)
-
- (when (scrollbar-abbreviated-p scroller)
- (let ((copy-size (scrollbar-anchor-width (getf *scrollbar-dimensions* scale))))
-
- (multiple-value-bind (from-x from-y copy-width copy-height)
- (if (eq :vertical orientation)
- (values 0 (+ copy-size copy-size) copy-size (+ copy-size 2))
- (values (+ copy-size copy-size) 0 (+ copy-size 2) copy-size))
-
- (multiple-value-bind (to-x to-y)
- (if (eq :vertical orientation)
- (values 0 copy-size)
- (values copy-size 0))
-
- (copy-area
- mask gc
- from-x from-y
- copy-width copy-height
- elevator
- to-x to-y))))))))
-
- ;; Dim arrows, if necessary
- (let ((insensitive-p (not (sensitive-p scroller))))
- (scrollbar-update-less-arrow scroller nil insensitive-p)
- (scrollbar-update-more-arrow scroller nil insensitive-p)))
-
-
-
- (defun scrollbar-update-less-arrow (scroller dim-p insensitive-p)
- (with-slots (value minimum foreground regions) (the scroller scroller)
- (unless (eq dim-p (or insensitive-p (= value minimum)))
-
- (multiple-value-bind (arrow-x arrow-y arrow-width arrow-height)
- (scrollbar-less-arrow-geometry scroller)
-
- (using-gcontext
- (gc :drawable scroller
- :function boole-xor
- :fill-style :stippled
- :foreground (logxor foreground (contact-current-background-pixel scroller))
- :stipple (contact-image-mask scroller 25%gray :depth 1))
-
- (draw-rectangle
- (svref regions *elevator-region*) gc
- arrow-x arrow-y
- arrow-width arrow-height
- :fill-p))))))
-
-
- (defun scrollbar-update-more-arrow (scroller dim-p insensitive-p)
- (with-slots (value maximum foreground regions) (the scroller scroller)
- (unless (eq dim-p (or insensitive-p (= value maximum)))
-
- (multiple-value-bind (arrow-x arrow-y arrow-width arrow-height)
- (scrollbar-more-arrow-geometry scroller)
-
- (using-gcontext
- (gc :drawable scroller
- :function boole-xor
- :fill-style :stippled
- :foreground (logxor foreground (contact-current-background-pixel scroller))
- :stipple (contact-image-mask scroller 25%gray :depth 1))
-
- (draw-rectangle
- (svref regions *elevator-region*) gc
- arrow-x arrow-y
- arrow-width arrow-height
- :fill-p))))))
-
-
- ;;;----------------------------------------------------------------------------+
- ;;; |
- ;;; Event Translations |
- ;;; |
- ;;;----------------------------------------------------------------------------+
-
- (defevent scroller (:motion-notify :button-1) scroller-handle-motion)
- (defevent scroller (:timer :update-delay) scroller-report-new-value)
- (defevent scroller (:timer :click) (throw-action :click-timeout))
- (defevent scroller (:button-release :button-1) scroller-handle-release)
- (defevent scroller (:button-press :button-1) scroller-handle-press)
-
- (defparameter *scroller-click-timeout* 0.2
- "Number of seconds to wait before starting continuous scrolling")
-
- (defparameter *scroller-hold-timeout* 0.05
- "Number of seconds to wait during continuous scrolling before updating value")
-
- (let ((press-handlers (make-array 7)))
- (flet
- ((find-region (scroller)
- ;; Return index of scroller region containing the current event
- (with-slots (regions orientation) scroller
- (with-event (child x y)
- (if child
-
- ;; Look up event child window among scroller regions
- (let ((region (position child regions :test #'eq)))
-
- (if (= region *elevator-region*)
- ;; Which part of elevator got the press: less-arrow, drag-area, or more-arrow?
- (let
- ((region (+ *less-arrow-region*
- (floor
- (- (if (eq :vertical orientation) y x)
- (scroller-value-position scroller))
- (scrollbar-anchor-width
- (getf *scrollbar-dimensions*
- (contact-scale scroller)))))))
- (if (and (= region *drag-area-region*)
- (scrollbar-abbreviated-p scroller))
- *more-arrow-region*
- region))
-
- ;; Min/max anchor press
- region))
-
- ;; Event occurred on non-child area of scroller
- *cable-region*))))
-
- (press-cable (scroller)
- (with-slots (orientation increment width height indicator-size update-delay display value) scroller
- (with-event (x y)
- (multiple-value-bind (event-position max-position)
- (if (eq :vertical orientation) (values y height) (values x width))
- (let*
- ((anchor-height (scrollbar-anchor-height
- (getf *scrollbar-dimensions* (contact-scale scroller))))
- (min-position anchor-height)
- (max-position (- max-position anchor-height))
- (pane-size (let ((size (true-indicator-size indicator-size)))
- (if (plusp size) size increment)))
- (pane-increment (if (< event-position (scroller-value-position scroller))
- ;; Decrement by pane?
- (when (>= event-position min-position)
- (- pane-size))
-
- ;; Increment by pane?
- (when (<= event-position max-position)
- pane-size))))
- (unless pane-increment
- ;; Just wait for release and do nothing
- (catch :release (loop (process-next-event display)))
- (return-from press-cable))
-
-
- (if (catch :release
- ;; If user is clicking fast on cable, then we can arrive here
- ;; before all :exposure's from previous clicks have been processed.
- ;; Therefore, must use a timer, so we can continue processing :exposure's
- ;; while waiting for click release.
- (add-timer scroller :click *scroller-click-timeout*)
- (unwind-protect
- (catch :click-timeout
- (loop (process-next-event display)))
- (delete-timer scroller :click))
- t)
-
- ;; Perform continuous pane scrolling...
- (let ((current-x x) (current-y y))
- ;; Set timer for update
- (when (and (numberp update-delay) (plusp update-delay))
- (add-timer scroller :update-delay update-delay))
-
- ;; Increment and warp pointer as needed, until release event
- (apply-callback scroller :begin-continuous)
- (catch :release
- (loop
- (scroller-increment-value scroller pane-increment)
- (multiple-value-setq (current-x current-y)
- (scrollbar-cable-warp
- scroller pane-increment
- current-x current-y
- min-position max-position))
-
- ;; Wait for timeout to elapse
- (do () ((not (process-next-event display *scroller-hold-timeout*))))))
- (apply-callback scroller :end-continuous))
-
- ;; Single click -- increment value
- (progn
- (scroller-increment-value scroller pane-increment)
-
- ;; Warp pointer to keep it between elevator and anchor
- (scrollbar-cable-warp
- scroller pane-increment
- x y
- min-position max-position)))
-
- ;; Report final value, if necessary
- (unless (eql 0 update-delay)
- (delete-timer scroller :update-delay)
- (apply-callback scroller :new-value value)))))))
-
- (press-drag-area (scroller)
- (with-slots (display regions value update-delay foreground orientation) scroller
- (let
- ((highlight-pixel (logxor foreground (contact-current-background-pixel scroller)))
- (elevator (svref regions *elevator-region*)))
-
- (multiple-value-bind (drag-x drag-y drag-width drag-height)
- (scrollbar-drag-area-geometry scroller)
-
- (using-gcontext
- (gc :drawable scroller
- :function boole-xor
- :foreground highlight-pixel)
-
- ;; Highlight drag area
- (draw-rectangle
- elevator gc
- drag-x drag-y drag-width drag-height :fill-p)
-
- (with-event (x y)
- (let ((*previous-position* (if (eq :vertical orientation) y x))
- (*drag-motion* t))
- (declare (special *previous-position* *drag-motion*))
-
- ;; Set timer for update
- (when (and (numberp update-delay) (plusp update-delay))
- (add-timer scroller :update-delay update-delay))
-
- ;; Handle motion events until release.
- (catch :release
- (loop (process-next-event display)))))
-
- ;; Report final value.
- (when (and (numberp update-delay) (plusp update-delay))
- (delete-timer scroller :update-delay))
- (apply-callback scroller :new-value value)
-
- ;; Unhighlight drag area
- (draw-rectangle
- elevator gc
- drag-x drag-y drag-width drag-height :fill-p))))))
-
- (press-less-arrow (scroller)
- (with-slots (display regions value maximum increment update-delay foreground orientation) scroller
- (let
- ((highlight-pixel (logxor foreground (contact-current-background-pixel scroller))))
-
- (multiple-value-bind (arrow-x arrow-y arrow-width arrow-height)
- (scrollbar-less-arrow-geometry scroller)
-
- (using-gcontext
- (gc :drawable scroller
- :function boole-xor
- :foreground highlight-pixel)
-
- ;; Highlight arrow
- (draw-rectangle
- (svref regions *elevator-region*) gc
- arrow-x arrow-y arrow-width arrow-height :fill-p)
-
- ;; Force pointer to stay within arrow window
- (grab-pointer scroller #.(make-event-mask :button-press :button-release)
- :confine-to (svref regions *less-arrow-region*))
-
- (if (catch :release (not (process-next-event display *scroller-click-timeout*)))
-
- ;; Perform continuous scrolling...
- (progn
- ;; Set timer for update
- (when (and (numberp update-delay) (plusp update-delay))
- (add-timer scroller :update-delay update-delay))
-
- ;; Increment until release event
- (apply-callback scroller :begin-continuous)
- (catch :release
- (loop
- (scroller-increment-value scroller (- increment))
- (do () ((not (process-next-event display *scroller-hold-timeout*))))))
- (apply-callback scroller :end-continuous))
-
- ;; Single click -- increment value.
- (scroller-increment-value scroller (- increment)))
-
- ;; Report final value, if necessary
- (unless (eql 0 update-delay)
- (delete-timer scroller :update-delay)
- (apply-callback scroller :new-value value))
-
- ;; Release grab
- (ungrab-pointer display)
-
- ;; Unhighlight arrow
- (draw-rectangle
- (svref regions *elevator-region*) gc
- arrow-x arrow-y arrow-width arrow-height :fill-p))))))
-
- (press-more-arrow (scroller)
- (with-slots (display regions value maximum increment update-delay foreground orientation) scroller
- (let
- ((highlight-pixel (logxor foreground (contact-current-background-pixel scroller))))
-
- (multiple-value-bind (arrow-x arrow-y arrow-width arrow-height)
- (scrollbar-more-arrow-geometry scroller)
-
- (using-gcontext
- (gc :drawable scroller
- :function boole-xor
- :foreground highlight-pixel)
-
- ;; Highlight arrow
- (draw-rectangle
- (svref regions *elevator-region*) gc
- arrow-x arrow-y arrow-width arrow-height :fill-p)
-
- ;; Force pointer to stay within arrow window
- (grab-pointer scroller #.(make-event-mask :button-press :button-release)
- :confine-to (svref regions *more-arrow-region*))
-
- (if (catch :release (not (process-next-event display *scroller-click-timeout*)))
-
- ;; Perform continuous scrolling...
- (progn
- ;; Set timer for update
- (when (and (numberp update-delay) (plusp update-delay))
- (add-timer scroller :update-delay update-delay))
-
- ;; Increment until release event
- (apply-callback scroller :begin-continuous)
- (catch :release
- (loop
- (scroller-increment-value scroller increment)
- (do () ((not (process-next-event display *scroller-hold-timeout*))))))
- (apply-callback scroller :end-continuous))
-
- ;; Single click -- increment value.
- (scroller-increment-value scroller increment))
-
- ;; Report final value, if necessary
- (unless (eql 0 update-delay)
- (delete-timer scroller :update-delay)
- (apply-callback scroller :new-value value))
-
- ;; Release grab
- (ungrab-pointer display)
-
- ;; Unhighlight arrow
- (draw-rectangle
- (svref regions *elevator-region*) gc
- arrow-x arrow-y arrow-width arrow-height :fill-p))))))
-
- (press-max-anchor (scroller)
- (with-slots (display regions foreground maximum value) scroller
- ;; Highlight max anchor
- (let
- ((max-anchor (svref regions *max-anchor-region*))
- (highlight-size (scrollbar-anchor-width
- (getf *scrollbar-dimensions* (contact-scale scroller)))))
-
- ;; This rectangle size is "too big", but we let the server clip it
- (using-gcontext (gc :drawable scroller :foreground foreground)
- (draw-rectangle
- max-anchor gc
- 0 0 highlight-size highlight-size
- :fill-p))
-
- ;; Wait for release event
- (catch :release
- (loop (process-next-event display)))
-
- ;; Unhighlight max anchor
- (clear-area max-anchor)
-
- ;; Go to maximum position
- (unless (= value maximum)
- (setf (scale-value scroller) maximum)
- (apply-callback scroller :new-value maximum)))))
-
- (press-min-anchor (scroller)
- (with-slots (display regions foreground minimum value) scroller
- ;; Highlight min anchor
- (let
- ((min-anchor (svref regions *min-anchor-region*))
- (highlight-size (scrollbar-anchor-width
- (getf *scrollbar-dimensions* (contact-scale scroller)))))
-
- ;; This rectangle size is "too big", but we let the server clip it
- (using-gcontext (gc :drawable scroller :foreground foreground)
- (draw-rectangle
- min-anchor gc
- 0 0 highlight-size highlight-size
- :fill-p))
-
- ;; Wait for release event
- (catch :release
- (loop (process-next-event display)))
-
- ;; Unhighlight min anchor
- (clear-area min-anchor)
-
- ;; Go to minimum position
- (unless (= value minimum)
- (setf (scale-value scroller) minimum)
- (apply-callback scroller :new-value minimum))))))
-
- ;; Initialize press-handlers dispatch vector
- (setf (svref press-handlers *elevator-region*) nil) ; should never be used!!
- (setf (svref press-handlers *min-anchor-region*) #'press-min-anchor)
- (setf (svref press-handlers *max-anchor-region*) #'press-max-anchor)
- (setf (svref press-handlers *less-arrow-region*) #'press-less-arrow)
- (setf (svref press-handlers *drag-area-region*) #'press-drag-area)
- (setf (svref press-handlers *more-arrow-region*) #'press-more-arrow)
- (setf (svref press-handlers *cable-region*) #'press-cable)
-
- ;; Define press action function
- (defun scroller-handle-press (scroller)
- (let ((*scroller-pressed-p* t))
- (declare (special *scroller-pressed-p*))
- (funcall (svref press-handlers (find-region scroller)) scroller)))))
-
- (defun scroller-handle-release (scroller)
- (declare (ignore scroller))
- (declare (special *scroller-pressed-p*))
- (when (boundp '*scroller-pressed-p*)
- (throw :release nil)))
-
- (defun scroller-handle-motion (scroller)
- (declare (special *previous-position* *drag-motion*))
- (when (boundp '*drag-motion*)
- (with-slots (orientation) (the scroller scroller)
- (with-event (state x y)
- (multiple-value-bind (ptr-x ptr-y)
- ;; Is :button-1 still down?
- (if (plusp (logand state #.(make-state-mask :button-1)))
-
- ;; Yes, query current pointer position
- (query-pointer scroller)
-
- ;; No, use final x,y returned for button transition
- (values x y))
-
- (let* ((new-position (if (eq :vertical orientation) ptr-y ptr-x))
- (increment (scroller-pixel-value scroller (- new-position *previous-position*))))
- (unless (zerop increment)
- (scroller-increment-value scroller increment)
- (setf *previous-position* new-position))))))))
-
-
- (defun scroller-report-new-value (scroller)
- (with-slots (value) (the scroller scroller)
- (apply-callback scroller :new-value value)))
-
- (defun scroller-increment-value (scroller increment)
- (with-slots (value minimum maximum update-delay) (the scroller scroller)
- (let*
- ((new-value (+ value increment))
- (adjusted (min (max (or (apply-callback scroller :adjust-value new-value)
- new-value)
- minimum)
- maximum)))
-
- (unless (= adjusted value)
- (setf (scale-value scroller) adjusted)
- (when (eql 0 update-delay)
- (apply-callback scroller :new-value adjusted))))))
-
-
- (defun scrollbar-cable-warp (scroller pane-increment current-x current-y min-position max-position)
- (with-slots (orientation) (the scroller scroller)
- (let*
- ((current-position
- (if (eq :vertical orientation) current-y current-x))
-
- (new-pointer-position
- (if (plusp pane-increment)
- (when (< current-position (setf min-position
- (+ (scroller-value-position scroller)
- (scrollbar-elevator-size scroller))))
- (1+ min-position))
-
- (when (> current-position (setf max-position
- (scroller-value-position scroller)))
- (1- max-position)))))
-
- (when new-pointer-position
- (if (eq :vertical orientation)
- (setf current-y new-pointer-position)
- (setf current-x new-pointer-position))
- (warp-pointer scroller current-x current-y))
-
- (values current-x current-y))))
-
-
- (defun scroller-value-position (scroller)
- (with-slots (width height orientation value minimum maximum) (the scroller scroller)
- (let*
- ((dimensions (getf *scrollbar-dimensions* (contact-scale scroller)))
- (margin (scrollbar-margin dimensions))
- (anchor-height (scrollbar-anchor-height dimensions))
- (range (- maximum minimum)))
-
- (+ anchor-height
- margin
- (if (zerop range) 0
- (pixel-round
- (* (- value minimum)
-
- ;; Pixels per value unit
- (/ (- (if (eq orientation :vertical) height width)
- anchor-height margin
- (* (scrollbar-anchor-width dimensions)
- (if (scrollbar-abbreviated-p scroller) 2 3))
- 2
- margin anchor-height)
- range))))))))
-
-
- (defun scroller-pixel-value (scroller pixels)
- (with-slots (width height orientation minimum maximum increment) (the scroller scroller)
- (let*
- ((dimensions (getf *scrollbar-dimensions* (contact-scale scroller)))
- (margin (scrollbar-margin dimensions))
- (anchor-height (scrollbar-anchor-height dimensions)))
-
- ;; pixels times value-units-per-pixel, rounded to nearest multiple of increment
- (* (pixel-round
- (/ (* pixels (- maximum minimum))
- (- (if (eq orientation :vertical) height width)
- anchor-height margin
- (* (scrollbar-anchor-width dimensions)
- (if (scrollbar-abbreviated-p scroller) 2 3))
- 2
- margin anchor-height))
- increment)
- increment))))
-
-
- (defun scroller-indicator-position (scroller &optional size)
- (setf size (or size (scroller-indicator-size scroller)))
-
- (with-slots (width height orientation value minimum maximum) (the scroller scroller)
- (let*
- ((dimensions (getf *scrollbar-dimensions* (contact-scale scroller)))
- (margin (scrollbar-margin dimensions))
- (anchor-height (scrollbar-anchor-height dimensions))
- (range (- maximum minimum)))
-
- (+ anchor-height
- margin
- (if (zerop range) 0
- (pixel-round
- (* (- value minimum)
-
- ;; Pixels per value unit for indicator position
- (/ (- (if (eq orientation :vertical) height width)
- anchor-height margin
- size
- margin anchor-height)
- range))))))))
-
- (defun scroller-indicator-size (scroller)
- (with-slots (width height orientation minimum maximum indicator-size) (the scroller scroller)
- (let*
- ((dimensions (getf *scrollbar-dimensions* (contact-scale scroller)))
- (margin (scrollbar-margin dimensions))
- (anchor-height (scrollbar-anchor-height dimensions))
- (range (- maximum minimum)))
-
- (pixel-round
- (*
- (min (true-indicator-size indicator-size) range) ; "clip" displayed size to cable range
- (if (zerop range) 0
- ;; Pixels per value unit for indicator size
- (/ (- (if (eq orientation :vertical) height width)
- anchor-height margin
- margin anchor-height)
- range)))))))
-
-